/*
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package jchess;

import java.awt.event.ComponentEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.JScrollPane;
import javax.swing.*;
import java.awt.*;
import java.io.File;
import java.io.BufferedReader;
import java.io.FileWriter;
import java.io.FileReader;
import java.io.IOException;
import java.util.Timer;
import java.util.Date;
import java.util.Calendar;
import java.util.ArrayList;
import java.util.List;
import java.awt.GridBagLayout;
import java.awt.event.ComponentListener;
import java.awt.image.BufferedImage;
import java.text.SimpleDateFormat;
import jchess.Player.colors;

/** Class responsible for the starts of new games, loading games,
 * saving it, and for ending it.
 * This class is also responsible for appoing player with have
 * a move at the moment
 */
public class Game extends JPanel implements MouseListener, ComponentListener {

    public Settings settings;
    public boolean blockedChessboard;
    public Chessboard chessboard;
    private Player activePlayer;
    public GameClock gameClock;
    public Client client;
    public Moves moves;
    public Chat chat;
    public List<Piece> pecasCapturadas;

    Game() {
        this.setLayout(null);
        this.moves = new Moves(this);
        settings = new Settings();
        chessboard = new Chessboard(this.settings, this.moves);
        chessboard.setVisible(true);
        chessboard.setSize(Chessboard.img_height, Chessboard.img_widht);
        chessboard.addMouseListener(this);
        chessboard.setLocation(new Point(0, 0));
        this.add(chessboard);
        //this.chessboard.
        gameClock = new GameClock(this);
        gameClock.setSize(new Dimension(200, 100));
        gameClock.setLocation(new Point(500, 0));
        this.add(gameClock);

        JScrollPane movesHistory = this.moves.getScrollPane();
        movesHistory.setSize(new Dimension(180, 350));
        movesHistory.setLocation(new Point(500, 121));
        this.add(movesHistory);

        JScrollPane jScrollPaneCapturadas = this.moves.getScrollPaneCapturadas();
        jScrollPaneCapturadas.setSize(new Dimension(100, 350));
        jScrollPaneCapturadas.setLocation(new Point(650, 100));
        this.add(jScrollPaneCapturadas);

        pecasCapturadas = new ArrayList<Piece>();

        this.chat = new Chat();
        this.chat.setSize(new Dimension(400, 100));
        this.chat.setLocation(new Point(0, 500));
        this.chat.setMinimumSize(new Dimension(400, 100));

        this.blockedChessboard = false;
        this.setLayout(null);
        this.addComponentListener(this);
        this.setDoubleBuffered(true);
    }

    /** Method to save actual state of game
     * @param path address of place where game will be saved
     */
    public void saveGame(File path, Boolean mostrarMensagem, Boolean notacaoCurta) {
        File file = path;
        FileWriter fileW = null;
        try {
            fileW = new FileWriter(file);
        } catch (java.io.IOException exc) {
            System.err.println("error creating fileWriter: " + exc);
            return;
        }
        Calendar cal = Calendar.getInstance();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy.MM.dd");
        String dataFormatada = sdf.format(cal.getTime());
        String str = new String("");
        String info = new String("[Event \"Game\"]\n"
                + "[Site \"Java Open Chess\"]\n"
                + "[Date \"" + dataFormatada + "\"]\n"
                + "[Round \"?\"]\n"
                + "[White \"" + this.settings.playerWhite.name + "\"]\n"
                + "[Black \"" + this.settings.playerBlack.name + "\"]\n"
                + "[Result \"*\"]\n\n");
        // alterar aqui para obter o resultado a partir da string de movimentos
        str += info;
        if (notacaoCurta) {
            str += this.moves.getMovesInStringNotacaoCurta();
        } else {
            str += this.moves.getMovesInString();
        }
        
        try {
            fileW.write(str);
            fileW.flush();
            fileW.close();
        } catch (java.io.IOException exc) {
            System.out.println("error writing to file: " + exc);
            JOptionPane.showMessageDialog(this, "error writing to file: " + exc);
            return;
        }
        if (mostrarMensagem) {
            JOptionPane.showMessageDialog(this, "Game seems to be saved properly.");
        }
    }

    /** Loading game method(loading game state from the earlier saved file)
     *  @param file File where is saved game
     */

    /*@Override
    public void setSize(int width, int height) {
    Dimension min = this.getMinimumSize();
    if(min.getHeight() < height && min.getWidth() < width) {
    super.setSize(width, height);
    } else if(min.getHeight() < height) {
    super.setSize(width, (int)min.getHeight());
    } else if(min.getWidth() < width) {
    super.setSize((int)min.getWidth(), height);
    } else {
    super.setSize(width, height);
    }
    }*/
    static public void loadGame(File file) {
        FileReader fileR = null;
        try {
            fileR = new FileReader(file);
        } catch (java.io.IOException exc) {
            System.out.println("Something wrong reading file: " + exc);
            return;
        }
        BufferedReader br = new BufferedReader(fileR);
        String tempStr = new String();
        String blackName, whiteName;
        try {
            tempStr = getLineWithVar(br, "[White");
            whiteName = getValue(tempStr);
            tempStr = getLineWithVar(br, "[Black");
            blackName = getValue(tempStr);
            tempStr = getLineWithVar(br, "1.");
        } catch (ReadGameError err) {
            System.out.println("Error reading file: " + err);
            return;
        }

        Game newGUI = null;
        if ("analiseJogadaJoChess.pgn".equals(file.getName())) {
            newGUI = JChessApp.jcv.addNewTab("Tab. Auxiliar " + whiteName + " vs. " + blackName);
        } else {
            newGUI = JChessApp.jcv.addNewTab(whiteName + " vs. " + blackName);
        }

        Settings locSetts = newGUI.settings;
        locSetts.playerBlack.name = blackName;
        locSetts.playerWhite.name = whiteName;
        locSetts.playerBlack.setType(Player.playerTypes.localUser);
        locSetts.playerWhite.setType(Player.playerTypes.localUser);
        locSetts.gameMode = Settings.gameModes.loadGame;
        locSetts.gameType = Settings.gameTypes.local;

        newGUI.newGame();
        newGUI.repaint();
        newGUI.blockedChessboard = true;
        newGUI.moves.setMoves(tempStr);
        newGUI.blockedChessboard = false;
        if ("analiseJogadaJoChess.pgn".equals(file.getName())) {
            newGUI.blockedChessboard = true;
        }
        //newGUI.chessboard.repaint();
        //newGUI.chessboard.draw();
    }

    static public void loadGamePersonalizado(File file, int numeroDeJogadas) {
        FileReader fileR = null;
        try {
            fileR = new FileReader(file);
        } catch (java.io.IOException exc) {
            System.out.println("Something wrong reading file: " + exc);
            return;
        }
        BufferedReader br = new BufferedReader(fileR);
        String tempStr = new String();
        String blackName, whiteName;
        try {
            tempStr = getLineWithVar(br, "[White");
            whiteName = getValue(tempStr);
            tempStr = getLineWithVar(br, "[Black");
            blackName = getValue(tempStr);
            tempStr = getLineWithVar(br, "1.");
        } catch (ReadGameError err) {
            System.out.println("Error reading file: " + err);
            return;
        }

        Game newGUI = null;
        newGUI = JChessApp.jcv.addNewTab("Tab. Auxiliar " + whiteName + " vs. " + blackName);

        Settings locSetts = newGUI.settings;
        locSetts.playerBlack.name = blackName;
        locSetts.playerWhite.name = whiteName;
        locSetts.playerBlack.setType(Player.playerTypes.localUser);
        locSetts.playerWhite.setType(Player.playerTypes.localUser);
        locSetts.gameMode = Settings.gameModes.loadGame;
        locSetts.gameType = Settings.gameTypes.local;

        newGUI.newGame();
        newGUI.repaint();
        newGUI.blockedChessboard = true;
        newGUI.moves.setMovesPersonalizado(tempStr, numeroDeJogadas);
        newGUI.blockedChessboard = false;
        if ("analiseJogadaJoChess.pgn".equals(file.getName())) {
            newGUI.blockedChessboard = true;
        }
    }

    /** Method checking in with of line there is an error
     *  @param  br BufferedReader class object to operate on
     *  @param  srcStr String class object with text which variable you want to get in file
     *  @return String with searched variable in file (whole line)
     *  @throws ReadGameError class object when something goes wrong when reading file
     */
    static public String getLineWithVar(BufferedReader br, String srcStr) throws ReadGameError {
        String str = new String();
        while (true) {
            try {
                str = br.readLine();
            } catch (java.io.IOException exc) {
                System.out.println("Something wrong reading file: " + exc);
            }
            if (str == null) {
                throw new ReadGameError();
            }
            if (str.startsWith(srcStr)) {
                return str;
            }
        }
    }

    /** Method to get value from loaded txt line
     *  @param line Line which is readed
     *  @return result String with loaded value
     *  @throws ReadGameError object class when something goes wrong
     */
    static public String getValue(String line) throws ReadGameError {
        //System.out.println("getValue called with: "+line);
        int from = line.indexOf("\"");
        int to = line.lastIndexOf("\"");
        int size = line.length() - 1;
        String result = new String();
        if (to < from || from > size || to > size || to < 0 || from < 0) {
            throw new ReadGameError();
        }
        try {
            result = line.substring(from + 1, to);
        } catch (java.lang.StringIndexOutOfBoundsException exc) {
            System.out.println("error getting value: " + exc);
            return "none";
        }
        return result;
    }

    /** Method to Start new game
     *
     */
    public void newGame() {
        chessboard.setPieces("", settings.playerWhite, settings.playerBlack);

        //System.out.println("new game, game type: "+settings.gameType.name());

        activePlayer = settings.playerWhite;
        if (activePlayer.playerType != Player.playerTypes.localUser) {
            this.blockedChessboard = true;
        }
        chessboard.repaint();
    }

    /** Method to end game
     *  @param message what to show player(s) at end of the game (for example "draw", "black wins" etc.)
     */
    public void endGame(String massage) {
        this.blockedChessboard = true;
        System.out.println(massage);
        JOptionPane.showMessageDialog(null, massage);
    }

    /** Method to swich active players after move
     */
    public void switchActive() {
        if (activePlayer == settings.playerWhite) {
            activePlayer = settings.playerBlack;
        } else {
            activePlayer = settings.playerWhite;
        }

        this.gameClock.switch_clocks();
    }

    /** Method of getting accualy active player
     *  @return  player The player which have a move
     */
    public Player getActivePlayer() {
        return this.activePlayer;
    }

    /** Method to go to next move (checks if game is local/network etc.)
     */
    public void nextMove() {
        switchActive();

        System.out.println("next move, active player: " + activePlayer.name + ", color: " + activePlayer.color.name() + ", type: " + activePlayer.playerType.name());
        if (activePlayer.playerType == Player.playerTypes.localUser) {
            this.blockedChessboard = false;
        } else if (activePlayer.playerType == Player.playerTypes.networkUser) {
            this.blockedChessboard = true;
        } else if (activePlayer.playerType == Player.playerTypes.computer) {
        }
    }

    /** Method to simulate Move to check if it's correct etc. (usable for network game).
     * @param beginX from which X (on chessboard) move starts
     * @param beginY from which Y (on chessboard) move starts
     * @param endX   to   which X (on chessboard) move go
     * @param endY   to   which Y (on chessboard) move go
     * */
    public boolean simulateMove(int beginX, int beginY, int endX, int endY) {
        try {
            chessboard.select(chessboard.squares[beginX][beginY]);
        } catch (ArrayIndexOutOfBoundsException exc) {
            System.out.println("Bad move: " + exc.getMessage());
            return false;
        }
        if (chessboard.activeSquare.getPiece().allMoves().indexOf(chessboard.squares[endX][endY]) != -1) //move
        {
            chessboard.move(chessboard.squares[beginX][beginY], chessboard.squares[endX][endY]);
        } else {
            System.out.println("Bad move");
            return false;
        }
        chessboard.unselect();
        nextMove();

        return true;
    }

    public void casasNeutras() {
        int i = 0, x = 0, y = 0, w = 0, z = 0, soma = 0;
        int matrizCasasNeutras[][] = new int[8][8];

        Piece peca = null;
        Piece pecaDefesa = null;
        Square squareAux = null;
        ArrayList list = null;

        King.analisandoCasasNeutras = true;

//-----------------------------------Ataques------------------------------------        

        //Percorre todas as casas, realizando todos os movimentos possiveis.
        //Portanto se sabe todos os ataques possiveis as peças do tabuleiro.
        for (y = 7; y >= 0; y--) {
            for (x = 0; x <= 7; x++) {
                peca = null;
                squareAux = null;
                list = null;
                peca = this.chessboard.squares[x][y].getPiece();

                if (peca != null) {

                    //Tratamento especial para o Peão, que toma de maneira diferente.
                    if ("Pawn".equals(peca.getName())) {
                        //Tratamento do Peão Branco.
                        if (peca.player.color == colors.white && y - 1 >= 0) {
                            soma = 1;
                            if (x - 1 >= 0) {
                                if (this.chessboard.squares[x - 1][y - 1].getPiece() == null || this.chessboard.squares[x - 1][y - 1].piece.player.color != peca.player.color) {
                                    matrizCasasNeutras[x - 1][y - 1] += soma;
                                }
                            }
                            if (x + 1 < 8) {
                                if (this.chessboard.squares[x + 1][y - 1].getPiece() == null || this.chessboard.squares[x + 1][y - 1].piece.player.color != peca.player.color) {
                                    matrizCasasNeutras[x + 1][y - 1] += soma;
                                }
                            }
                        }
                        //Tratamento do Peão Preto.
                        if (peca.player.color == colors.black && y + 1 < 8) {
                            soma = -1;
                            if (x - 1 >= 0) {
                                if (this.chessboard.squares[x - 1][y + 1].getPiece() == null || this.chessboard.squares[x - 1][y + 1].piece.player.color != peca.player.color) {
                                    matrizCasasNeutras[x - 1][y + 1] += soma;
                                }
                            }
                            if (x + 1 < 8) {
                                if (this.chessboard.squares[x + 1][y + 1].getPiece() == null || this.chessboard.squares[x + 1][y + 1].piece.player.color != peca.player.color) {
                                    matrizCasasNeutras[x + 1][y + 1] += soma;
                                }
                            }
                        }
                    } else {
                        //Lista todos os movimenatos da peça(menos o Peão, tratado especialmente).
                        list = this.chessboard.squares[x][y].getPiece().allMoves();

                        if (peca.player.color == Player.colors.white) {
                            soma = 1;
                        } else {
                            soma = -1;
                        }

                        //Se for possivel movimentar a peça, soma na casa destino na matrizAuxiliar.
                        if (!list.isEmpty()) {
                            for (i = 0; i < list.size(); i++) {
                                squareAux = (Square) list.get(i);
                                matrizCasasNeutras[squareAux.getPozX()][squareAux.getPozY()] += soma;
                            }
                        }
                    }
                }
            }
        }

        //----------------------------------Defesas-----------------------------

        //Percorre todas as casas, onde existir uma peça, ela é retirada do 
        //tabuleiro e é movimentado o restante das peças da mesma cor, 
        //descobrindo as defesas da casa.
        for (y = 7; y >= 0; y--) {
            for (x = 0; x <= 7; x++) {
                pecaDefesa = null;
                pecaDefesa = this.chessboard.squares[x][y].getPiece();

                if (pecaDefesa != null && !"King".equals(pecaDefesa.getName())) {

                    this.chessboard.squares[x][y].piece = null;

                    for (z = 7; z >= 0; z--) {
                        for (w = 0; w <= 7; w++) {
                            peca = null;
                            squareAux = null;
                            list = null;

                            peca = this.chessboard.squares[w][z].getPiece();

                            if (peca != null) {

                                if (peca.player.color == pecaDefesa.player.color) {

                                    //Tratamento especial para o Peão, que toma de maneira diferente.
                                    if ("Pawn".equals(peca.getName())) {
                                        //Tratamento do Peão Branco.
                                        if (peca.player.color == colors.white && z - 1 >= 0) {
                                            soma = 1;
                                            if (w - 1 >= 0) {
                                                if (this.chessboard.squares[w - 1][z - 1].getPiece() == null || this.chessboard.squares[w - 1][z - 1].piece.player.color != peca.player.color) {
                                                    if ((w - 1 == x) && (z - 1 == y)) {
                                                        matrizCasasNeutras[w - 1][z - 1] += soma;
                                                    }
                                                }
                                            }
                                            if (w + 1 < 8) {
                                                if (this.chessboard.squares[w + 1][z - 1].getPiece() == null || this.chessboard.squares[w + 1][z - 1].piece.player.color != peca.player.color) {
                                                    if ((w + 1 == x) && (z - 1 == y)) {
                                                        matrizCasasNeutras[w + 1][z - 1] += soma;
                                                    }
                                                }
                                            }
                                        }
                                        //Tratamento do Peão Preto.
                                        if (peca.player.color == colors.black && z + 1 < 8) {
                                            soma = -1;
                                            if (w - 1 >= 0) {
                                                if (this.chessboard.squares[w - 1][z + 1].getPiece() == null || this.chessboard.squares[w - 1][z + 1].piece.player.color != peca.player.color) {
                                                    if ((w - 1 == x) && (z + 1 == y)) {
                                                        matrizCasasNeutras[w - 1][z + 1] += soma;
                                                    }
                                                }
                                            }
                                            if (w + 1 < 8) {
                                                if (this.chessboard.squares[w + 1][z + 1].getPiece() == null || this.chessboard.squares[w + 1][z + 1].piece.player.color != peca.player.color) {
                                                    if ((w + 1 == x) && (z + 1 == y)) {
                                                        matrizCasasNeutras[w + 1][z + 1] += soma;
                                                    }
                                                }
                                            }
                                        }
                                    } else {

                                        list = this.chessboard.squares[w][z].getPiece().allMoves();

                                        if (!list.isEmpty()) {
                                            //se for peça branca, soma 1, senão diminui 1.
                                            if (peca.player.color == colors.white) {
                                                soma = 1;
                                            } else {
                                                soma = -1;
                                            }
                                            for (i = 0; i < list.size(); i++) {
                                                squareAux = (Square) list.get(i);
                                                if (squareAux.getPozX() == x && squareAux.getPozY() == y) {
                                                    matrizCasasNeutras[x][y] += soma;
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                    this.chessboard.squares[x][y].piece = pecaDefesa;
                }
            }
        }

        King.analisandoCasasNeutras = false;
        //Ao final é desenhado o N nas casas neutras.
        chessboard.desenhaCasasNeutras(matrizCasasNeutras);
    }

    // MouseListener:
    public void mouseClicked(MouseEvent arg0) {
    }

    public void mousePressed(MouseEvent event) {
        if (event.getButton() == MouseEvent.BUTTON3) //right button
        {
            if (chessboard.undo()) {
                this.switchActive();
            }
        } else //left button
        {
            if (!blockedChessboard) {
                int x = event.getX();//get X position of mouse
                int y = event.getY();//get Y position of mouse

                Square sq = chessboard.getSquare(x, y);
                if ((sq.getPiece() == null && chessboard.activeSquare == null)
                        || (this.chessboard.activeSquare == null && sq.getPiece() != null && sq.getPiece().player != this.activePlayer)) {
                    return;
                }

                if (sq.getPiece() != null && sq.getPiece().player == this.activePlayer && sq != chessboard.activeSquare) {
                    chessboard.unselect();
                    chessboard.select(sq);
                } else if (chessboard.activeSquare == sq) //unselect
                {
                    chessboard.unselect();
                } else if (chessboard.activeSquare.getPiece().allMoves().indexOf(sq) != -1) //move
                {
                    if (settings.gameType == Settings.gameTypes.local) {
                        chessboard.move(chessboard.activeSquare, sq);
                    } else if (settings.gameType == Settings.gameTypes.network) {
                        client.sendMove(chessboard.activeSquare.getPozX(), chessboard.activeSquare.getPozY(), sq.getPozX(), sq.getPozY());
                        chessboard.move(chessboard.activeSquare, sq);
                    }

                    chessboard.unselect();

                    //switch player
                    this.nextMove();

                    //checkmate or stalemate
                    King king;
                    if (this.activePlayer == settings.playerWhite) {
                        king = chessboard.kingWhite;
                    } else {
                        king = chessboard.kingBlack;
                    }

                    switch (king.isCheckmatedOrStalemated()) {
                        case 1:
                            this.endGame("Checkmate! " + king.player.color.toString() + " player lose!");
                            break;
                        case 2:
                            this.endGame("Stalemate! Draw!");
                            break;
                    }
                }
            } else if (blockedChessboard) {
                System.out.println("Chessboard is blocked");
            }
        }
    }

    public void mouseReleased(MouseEvent arg0) {
    }

    public void mouseEntered(MouseEvent arg0) {
    }

    public void mouseExited(MouseEvent arg0) {
    }

    public void componentResized(ComponentEvent e) {
        int height = this.getHeight() >= this.getWidth() ? this.getWidth() : this.getHeight();
        int chess_height = (int) (height * 0.8);
        this.chessboard.resizeChessboard(chess_height);
        chess_height = this.chessboard.getHeight();
        this.moves.getScrollPane().setLocation(new Point(chess_height + 5, 100));
        this.moves.getScrollPane().setSize(this.moves.getScrollPane().getWidth(), chess_height - 100);
        this.gameClock.setLocation(new Point(chess_height + 5, 0));
        if (this.chat != null) {
            this.chat.setLocation(new Point(0, chess_height + 5));
            this.chat.setSize(new Dimension(chess_height, this.getHeight() - (chess_height + 5)));
        }
    }

    public void componentMoved(ComponentEvent e) {
    }

    public void componentShown(ComponentEvent e) {
    }

    public void componentHidden(ComponentEvent e) {
    }
}

class ReadGameError extends Exception {
}